package parser;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import modex.Document;
import modex.Paragraphe;
import modex.Terme;
import modex.TermePara;

import java.text.Normalizer;
import java.util.regex.Pattern;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

// Parser pour récupérer les termes et calculer leur poids

public class ParserTermeHandler extends DefaultHandler {

    // On stocke les informations du parsage dans ces listes d'objets :
    private ArrayList<Document> documents = new ArrayList<Document>();
    private HashMap<Integer, Paragraphe> paragraphes = new HashMap<Integer, Paragraphe>();
    private HashMap<String, Terme> termes = new HashMap<String, Terme>();
    private HashMap<String, TermePara> termeParas = new HashMap<String, TermePara>();

    // Flags nous indiquant la position du parseur
    private boolean inRecit, inDescription, inSec;

    // Variable pour gérer les ids des objets
    private static int idDocument = 1, idParagraphe = 1, idTerme = 1;

    // Variable pour stocker les id dans le xPath
    private int idPinRecit = 0, idPinSEC = 0, idPinDescription = 0, idSEC = 0;

    //buffer nous permettant de récupérer les données 
    private StringBuffer buffer;

    //buffer nous permettant de stocker le PATH 
    private Stack<String> tagStack;

    // ArrayList permettant de stocker la stop list
    private ArrayList<String> stopList;
    
    // RegExp utilisée par le Parser et le module de recherche
    public static final String REGEXP = " |\\*|\\$|%|/|\\+|'|«|»|,|\\?|;|\\.|:|!|-|_|\\)|\\(|\\[|\\]|\n|\t|@|="; // Enlever "
    
//    public static final String REGEXP = " |'|«|»|,|\\?|;|\\.|:|!|-|_|\\)|\\(|\\[|\\]|\n|\t|\\+|\\$|@|%|=|\\*|/|\\.\\.\\.";
    

	// simple constructeur
	public ParserTermeHandler(){
		super();

    	// Initialisation de la pile contenant les balises rencontrées
    	this.tagStack = new Stack<String>();

    	// Initialisation de la stopList
    	if (stopList == null){
	    	this.stopList = new ArrayList<String>();
	    	this.initStopList();
    	}

    	// Initialisation des Listes d'objets
    	if (documents == null){
    		this.documents = new ArrayList<Document>();
    	}
    	if (paragraphes == null){
    		this.paragraphes = new HashMap<Integer, Paragraphe>();
    	}
    	if (termes == null){
    		this.termes = new HashMap<String, Terme>();
    	}
    	if (termeParas == null){
    		this.termeParas = new HashMap<String, TermePara>();
    	}
	}

	// Début du parsage d'un document
    @Override
    public void startDocument() throws SAXException {
    	//TODO Remove
//    	System.out.println("Début du parsage");
    }

    // Fin du parsing
    @Override
    public void endDocument() throws SAXException {
    	//TODO Remove
//    	System.out.println("Fin du parsage\n");

//    	System.out.println("Current document : "+this.documents.get(this.documents.size()-1).getNomDocument());
//    	System.out.println("DOC LIST \t\t: "+documents.size() + " documents");	
//    	System.out.println("PARA LIST \t\t: "+paragraphes.size() + " paragraphes");
//    	System.out.println("TERMPARAS LIST \t\t: "+termeParas.size() + " terme-paragraphes");
//    	System.out.println("TERMS LIST \t\t: "+termes.size() + " termes");
//		System.out.println("STOP LIST \t\t: "+stopList.size() + " éléments\n\n");
		
    }

    // Contenu d'une balise (A chaque fois que le parser rencontre du texte (#PCDATA))
    @Override
    public void characters(char[] ch,int start, int length) throws SAXException {

    	// Récupération de la chaîne de caractères (le contenu d'une balise)
    	String lecture = new String(ch,start,length);
    	
		if(buffer != null) buffer.append(lecture);
    }

    // Ouverture de balise
    // qName : nom de la balise
    // attributes : attributs de la balise
    @Override
    public void startElement(String uri, String localName,
                    String qName, Attributes attributes) throws SAXException {

    	// Ajout à la pile de la balise trouvée
    	this.tagStack.push(qName);
    	
    	// Balises ne contenant pas de texte :

		if (qName.equals("RECIT")) {
 	        inRecit = true;

		} else if (qName.equals("DESCRIPTION")) {
	        inDescription = true; 

	    } else if (qName.equals("SEC")) {
	    	idSEC++;
	        inSec = true; 
  
    	} else {
			// Balises contenant du texte :
	
			if(qName.equals("P")){
				buffer = new StringBuffer();
				
				if (inSec) {
					idPinSEC++;
				} else if (inDescription) {
					idPinDescription++;
				} else {
					idPinRecit++;
				}

			} else if (qName.equals("TITRE")){
				buffer = new StringBuffer();

			} else if (qName.equals("SOUS TITRE")){
				buffer = new StringBuffer();
			}
        }
    }
    
    // Fin de balise
    // qName : nom de la balise
    @Override
    public void endElement(String uri, String localName, String qName)
                    throws SAXException {

        if (qName.equals("RECIT")) {
	    	idPinRecit=0;
	    	idSEC=0;
	        inRecit = false;
  
	    } else if (qName.equals("DESCRIPTION")) {
	    	idPinDescription=0;
	        inDescription = false; 
	        
	    } else if (qName.equals("SEC")) {
	    	idPinSEC=0;
	        inSec = false;   
                
        } else if (qName.equals("P")){
        	// Récupération du xPath
        	String path = this.generatexPath();

        	// Création du paragraphe 
        	this.createParagraph(path);

        	// On traite la chaîne de caractères présente dans le buffer
        	this.extractTermsFromBuffer(idParagraphe);

        	// On réinitialise le buffer à null
			buffer = null;

        } else if (qName.equals("TITRE")){
        	// Récupération du xPath
        	String path = this.generatexPath();
        	
        	// Création du paragraphe 
        	this.createParagraph(path);
        	
        	// On traite la chaîne de caractères présente dans le buffer
        	this.extractTermsFromBuffer(idParagraphe);

        	// On réinitialise le buffer à null
			buffer = null;

        } else if(qName.equals("SOUS-TITRE")){
        	// Récupération du xPath
        	String path = this.generatexPath();
        	
        	// Création du paragraphe 
        	this.createParagraph(path);
        	
        	// On traite la chaîne de caractères présente dans le buffer
        	this.extractTermsFromBuffer(idParagraphe);

        	// On réinitialise le buffer à null
			buffer = null;
        }

    	// Dépilage de la balise
    	this.tagStack.pop();
    }

    private void extractTermsFromBuffer(int idP) {

    	if ((buffer != null) && (buffer.toString() != null)) {
    		
			// Récupération des termes du paragraphe grâce à la regExp
	    	String[] possibleTerms = buffer.toString().split(ParserTermeHandler.REGEXP, -1);
			
	    	// Compteur permettant de connaître la position du terme
	    	Integer position = new Integer(1);
	    	
	    	// Id d'un terme déjà présent
	    	int idTermAlreadyMatched = 0;
	    	
	    	// Terme récupéré depuis la HashMap
	    	Terme termAlreadyMatched = null;
	    	
	    	// TermePara déjà présent
	    	TermePara tpAlreadyMatched = new TermePara();
	    	
			// Nombre total de termes (différents) dans le paragraphe
			int nbTermsInPara=0;
			
	    	// Parcours du tableau résultat
			for (String termeValue : possibleTerms){

				// On rejette les termes "vides" et les retour ligne
				if (!termeValue.equals("") && !termeValue.equals("\n")) {
					
					// Ajout des Termes à l'ArrayList "termes" après exclusion de ceux qui sont dans la stopList et de ceux qui sont déjà présents
					if (!stopList.contains(termeValue.trim().toLowerCase())) {

						// On enlève les espaces inutiles, les accents et les majuscules
						String myNewTermValue = this.deAccent(termeValue.trim().toLowerCase());
						
						// Lemmatisation du terme
						if (termeValue.trim().length() > 7) {
							myNewTermValue = myNewTermValue.substring(0, 7);
						}

						// Vérification de la présence de la valeur du terme 
						Boolean existsInTerme = false;
								
						// On vérifie la présence du terme dans la HashMap
						if ((termAlreadyMatched = this.getTermes().get(myNewTermValue)) != null){
							existsInTerme = true;
							idTermAlreadyMatched = termAlreadyMatched.getIdTerme();
						}
												
						if (!existsInTerme){
							/* Le mot n'existe pas dans la liste */
//							System.out.println("Nouveau terme dans la liste : " + myNewTermValue);
							
							// Ajout du terme
							this.termes.put(myNewTermValue, new Terme(idTerme, myNewTermValue, 1)); // Nouveau terme donc il apparait dans un seul paragraphe pour l'instant

							// Ajout de termepara dans l'arraylist avec poids = 1(ere occurr)
							// La key de la hashMap est "idT idP"
							this.termeParas.put(idTerme+" "+(idParagraphe-1) ,new TermePara(idTerme, idParagraphe-1, 1, position.toString())); // -1 car on l'a incrémenté avant
							
							// On incrémente le nombre de termes du paragraphe et l'id terme
							nbTermsInPara++;
							idTerme++;
							
						} else {		
							/* Le mot existe en base */
//							System.out.println("Mot : " + myNewTermValue + " existe deja dans la liste des termes");
							
							// Vérification de la présence du terme dans la table TermePara 
							Boolean existsInTermePara = false;
							
							// On vérifie la présence du termePara dans la HashMap
							if ((tpAlreadyMatched = this.getTermeParas().get(idTermAlreadyMatched+" "+(idParagraphe-1))) != null){
								existsInTermePara = true;
							}

							if (!existsInTermePara) {
								/* Le mot n'existe pas dans le paragraphe */
//								System.out.println("Nouveau mot dans le paragraphe : " + myNewTermValue
//										+ " à la position " + position.toString()
//										);
								
								// Ajout de termepara dans la bdd avec poids = 1(ere occurr)
								this.termeParas.put(idTermAlreadyMatched+" "+(idParagraphe-1) ,new TermePara(idTermAlreadyMatched, idParagraphe-1, 1, position.toString()));
								
								// Nouveau paragraphe : on incrémente le nombre de paragraphes dans lequel on retrouve le terme
								termAlreadyMatched.setNbParagraphes(termAlreadyMatched.getNbParagraphes()+1);
								
								// On incrémente le nombre de termes du paragraphe
								nbTermsInPara++;
							}
							else {
								/* Le mot existe dans le paragraphe */
//								System.out.println("Mot : " + myNewTermValue + " deja dans le para, " +
//										"poids = " + tpAlreadyMatched.getPoids());
//								System.out.println("Positions : " + tpAlreadyMatched.getPosition());
								
								// Mise à jour du poids = poids + 1
								tpAlreadyMatched.setPoids(tpAlreadyMatched.getPoids()+1);
//								System.out.println("\tNouveau poids : " + tpAlreadyMatched.getPoids());
								
								// Ajout nouvelle position rencontree								
								tpAlreadyMatched.setPosition(tpAlreadyMatched.getPosition().concat(","+position.toString()));
//								System.out.println("\tPositions : " + tpAlreadyMatched.getPosition());
							}							
						}
					}
					
					position++;
				}
			}

			// Mise à jour du nombre de terme du paragraphe nécessaire au calcul de TF IDF
			this.getParagraphes().get(idParagraphe-1).setNbTermes(nbTermsInPara); // Paragraphe traité = id -1 (on l'a incrémenté à la création)
    	}
    }
    
    private String generatexPath() {

    	// Création d'un tableau permettant de récupérer les balises contenues dans la Pile
    	String[] tags = new String[tagStack.size()];

    	this.tagStack.copyInto(tags);
    	String xPath = new String();

    	for (String tag : tags){
    		
    		// Traitement spécial pour les balises P
    		if (tag.equals("P")) {
    			if (inSec) {
    				xPath += "/" + tag + "["+ idPinSEC +"]";
    			} else if (inDescription) {
    				xPath += "/" + tag + "["+ idPinDescription +"]";
    			} else {
    				xPath += "/" + tag + "["+ idPinRecit +"]";
    			}
    			
    		} else if (tag.equals("SEC")) {
    			xPath += "/" + tag + "["+ idSEC +"]";
    			
    		} else {
	    		xPath += "/" + tag + "[1]";
    		}
    	}

    	return xPath;
    }
    
    private void initStopList(){
    	
    	BufferedReader br = null;
		try {
			// Récupération du fichier
			InputStream is = new FileInputStream("resources/StopList.txt");
			 
			// Récupération d'un BufferedReader pour lire le fichier
			InputStreamReader isr = new InputStreamReader(is);
			br = new BufferedReader(isr);
			String line = "";
			
			// Parcours du fichier ligne par ligne et ajout des termes à l'ArrayList
		    while ((line = br.readLine()) != null) {
		        this.stopList.add(line);
		    }
		
		} catch (IOException e) {
		    System.out.println(e);
		} finally {
		     try {
		         if (br != null) {
		             br.close();
		         }
		     } catch (IOException e) {
		    	 System.out.println(e);
		     }
		}
    }

    private void createParagraph(String xPath){
    	
		Paragraphe para = new Paragraphe();
	   	para.setIdParagraphe(idParagraphe);
	   	idParagraphe++;
	   	para.setxPath(xPath);
	   	para.setIdDocument((idDocument-1)); // -1 car on l'a incrémenté au début du parsage

	   	this.getParagraphes().put(idParagraphe-1,para); // -1 car on l'a incrémenté juste avant
   }
    
   public void createDocument(String docName){
        Document doc = new Document();

        // On enlève l'extension .xml pour le nom du doc
        doc.setIdDocument(idDocument);
        doc.setNomDocument(docName.substring(0, docName.indexOf(".xml")));
        idDocument++;

        this.documents.add(doc);
   }
   
   /**
    * Used to get the string without accents
    * 
    * @param str, the string to deAccent
    * @return the deAccented string
    */
   public String deAccent(String str) {
	    String nfdNormalizedString = Normalizer.normalize(str, Normalizer.Form.NFD); 
	    Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
	    return pattern.matcher(nfdNormalizedString).replaceAll("");
	}
	
	public ArrayList<Document> getDocuments() {
		return documents;
	}
	
	public HashMap<Integer, Paragraphe> getParagraphes() {
		return paragraphes;
	}
	
	public HashMap<String, TermePara> getTermeParas() {
		return termeParas;
	}
	
	public HashMap<String, Terme> getTermes() {
		return termes;
	}
	
	public ArrayList<String> getStopList() {
		return stopList;
	}

}